package model

import (
	"os"
	"sort"
	"strings"
	"time"

	"file_explorer/common/consts"
	"file_explorer/common/util"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/theme"
	"github.com/spf13/cast"
)

type FileInfo struct {
	Name        string
	Dir         string
	Type        byte
	Size        int64
	Unavailable bool
	ModifyTime  time.Time
	Metadata    *FileMetadata
}

func (info *FileInfo) GetTypeStr() string {
	if info.IsDir() {
		return consts.FileTypeStrDir
	}
	return consts.FileTypeStrRegular
}

func (info *FileInfo) GetSize() string {
	if info.IsDir() {
		return ""
	}
	return util.ConvSize(info.Size)
}

func (info *FileInfo) GetModifyTime() string {
	if info.Type == consts.FileTypeDriver {
		return ""
	}
	return info.ModifyTime.Format(time.DateTime)
}

func (info *FileInfo) GetScore() int {
	if info.Metadata == nil || info.Metadata.Score == nil {
		return 0
	}
	return *info.Metadata.Score
}
func (info *FileInfo) GetScoreStr() string {
	if info.Metadata == nil || info.Metadata.Score == nil {
		return ""
	}
	return cast.ToString(info.Metadata.Score)
}

func (info *FileInfo) GetTagsStr() string {
	if info.Metadata == nil {
		return ""
	}
	return strings.Join(info.Metadata.Tags, "; ")
}

func (info *FileInfo) GetTagList() []string {
	if info.Metadata == nil {
		return nil
	}
	return info.Metadata.Tags
}

func (info *FileInfo) GetUrl() string {
	if info.Metadata == nil {
		return ""
	}
	return cast.ToString(info.Metadata.Url)
}

func (info *FileInfo) GetNote() string {
	if info.Metadata == nil {
		return ""
	}
	return cast.ToString(info.Metadata.Note)
}

func (info *FileInfo) SetScore(score *int) {
	if info.Metadata == nil {
		info.Metadata = &FileMetadata{}
	}
	info.Metadata.Score = score
}

func (info *FileInfo) SetTags(tags []string) {
	if info.Metadata == nil {
		info.Metadata = &FileMetadata{}
	}
	sort.Slice(tags, util.GetComparatorByPriority[int](
		true,
		func(i, j int) int {
			return len(tags[j]) - len(tags[i])
		},
		func(i, j int) int {
			return util.CmpText(tags[i], tags[j])
		},
	))
	info.Metadata.Tags = tags
}
func (info *FileInfo) SetNote(note *string) {
	if info.Metadata == nil {
		info.Metadata = &FileMetadata{}
	}
	info.Metadata.Note = note
}
func (info *FileInfo) SetUrl(url *string) {
	if info.Metadata == nil {
		info.Metadata = &FileMetadata{}
	}
	info.Metadata.Url = url
}

func (info *FileInfo) GetPath() string {
	return util.PathJoin(info.Dir, info.Name)
}

func (info *FileInfo) SplitFilename() (string, string) {
	if info.IsDir() {
		return info.Name, ""
	}
	return util.SplitFilename(info.Name)
}

func (info *FileInfo) IsParent(childPath string) bool {
	return strings.HasPrefix(childPath, info.GetPath()+"/")
}

func (info *FileInfo) IsChild(parentPath string) bool {
	return strings.HasPrefix(info.GetPath(), parentPath+"/")
}

func (info *FileInfo) GetIcon() fyne.Resource {
	if info.Unavailable {
		return theme.ErrorIcon()
	}
	if info.Type == consts.FileTypeDir {
		return theme.FolderIcon()
	}
	if info.Type == consts.FileTypeVirtual {
		return theme.VisibilityOffIcon()
	}
	if info.Type == consts.FileTypeDriver {
		return theme.ComputerIcon()
	}

	_, extName := util.SplitFilename(info.Name)

	if util.InStrArray(extName, consts.ExtNameText) >= 0 {
		return theme.FileTextIcon()
	}

	if util.InStrArray(extName, consts.ExtNameDocument) >= 0 {
		return theme.DocumentIcon()
	}

	if util.InStrArray(extName, consts.ExtNameImage) >= 0 {
		return theme.MediaPhotoIcon()
	}

	if util.InStrArray(extName, consts.ExtNameVideo) >= 0 {
		return theme.MediaVideoIcon()
	}

	if util.InStrArray(extName, consts.ExtNameAudio) >= 0 {
		return theme.MediaMusicIcon()
	}

	return theme.FileIcon()
}

func (info *FileInfo) FormatMetadata() {
	if info.Metadata == nil {
		return
	}

	if info.Metadata.Note != nil && len(*info.Metadata.Note) == 0 {
		info.Metadata.Note = nil
	}
	if len(info.Metadata.Tags) == 0 {
		info.Metadata.Tags = nil
	}
	if info.Metadata.Url != nil && len(*info.Metadata.Url) == 0 {
		info.Metadata.Tags = nil
	}

	if info.Metadata.Note == nil &&
		info.Metadata.Tags == nil &&
		info.Metadata.Score == nil &&
		info.Metadata.Url == nil {
		info.Metadata = nil
	}
}

func (info *FileInfo) IsDir() bool {
	return info.Type == consts.FileTypeDir || info.Type == consts.FileTypeDriver
}

func (info *FileInfo) Clone() *FileInfo {
	clone := &FileInfo{
		Name:        info.Name,
		Dir:         info.Dir,
		Type:        info.Type,
		Size:        info.Size,
		ModifyTime:  info.ModifyTime,
		Unavailable: info.Unavailable,
	}
	if info.Metadata != nil {
		clone.Metadata = &FileMetadata{}
		if info.Metadata.Note != nil {
			note := *info.Metadata.Note
			clone.Metadata.Note = &note
		}
		if len(info.Metadata.Tags) > 0 {
			clone.Metadata.Tags = make([]string, len(info.Metadata.Tags))
			for i, tag := range info.Metadata.Tags {
				clone.Metadata.Tags[i] = tag
			}
		}
		if info.Metadata.Score != nil {
			score := *info.Metadata.Score
			clone.Metadata.Score = &score
		}
		if info.Metadata.Url != nil {
			url := *info.Metadata.Url
			clone.Metadata.Url = &url
		}
	}
	return clone
}

func BuildFileInfo(dir string, entry os.DirEntry) *FileInfo {
	fileInfo := &FileInfo{
		Name: entry.Name(),
		Dir:  dir,
		Type: consts.FileTypeRegular,
	}

	rawInfo, err := entry.Info()
	if err == nil {
		fileInfo.Size = rawInfo.Size()
		fileInfo.ModifyTime = rawInfo.ModTime()
	} else {
		fileInfo.Unavailable = true
	}

	if entry.IsDir() {
		fileInfo.Type = consts.FileTypeDir
		fileInfo.Size = 0
	}

	return fileInfo
}

func BatchGetTagList(fileInfos []*FileInfo) []string {
	tagSet := make(map[string]struct{})
	for _, fileInfo := range fileInfos {
		for _, tag := range fileInfo.GetTagList() {
			tagSet[tag] = struct{}{}
		}
	}
	tagList := make([]string, 0, len(tagSet))
	for tag := range tagSet {
		tagList = append(tagList, tag)
	}

	// 排序
	sort.Slice(tagList, func(i, j int) bool {
		return util.CmpText(tagList[i], tagList[j]) > 0
	})

	return tagList
}
